﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Runtime.CompilerServices;
using FLib;
using LibraryGuard.Interfaces;

namespace LibraryGuard
{
	/// <summary>
	/// Action list implementation.
	/// Each action in this implementation is exclusive.
	/// </summary>
	/// <remarks>
	/// If you want to iterate over list, lock on object.
	/// </remarks>
	public sealed class ActionList
		: IActionList
	{
		#region Private fields
		private readonly List<IActionInfo> InnerList = new List<IActionInfo>();
		#endregion

		#region INotifyCollectionChanged Members
		/// <inheritdoc />
		public event NotifyCollectionChangedEventHandler CollectionChanged;
		#endregion

		#region IActionList Members
		/// <inheritdoc />
		[MethodImpl(MethodImplOptions.Synchronized)]
		public bool Add(IActionInfo action)
		{
			return this.Add(action, this.CompareIds);
		}

		/// <inheritdoc />
		[MethodImpl(MethodImplOptions.Synchronized)]
		public bool Add(IActionInfo action, Comparison<object> idComparer)
		{
			if (this.Contains(action, idComparer))
				return false;
			this.InnerList.Add(action);
			this.CollectionChanged.Raise(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, action, this.Count - 1));
			return true;
		}

		/// <inheritdoc />
		[MethodImpl(MethodImplOptions.Synchronized)]
		public void Replace(IActionInfo action)
		{
			this.Replace(action, this.CompareIds);
		}

		/// <inheritdoc />
		[MethodImpl(MethodImplOptions.Synchronized)]
		public void Replace(IActionInfo action, Comparison<object> idComparer)
		{
			Validate.Debug(() => action, v => v.NotNull());
			Validate.Debug(() => idComparer, v => v.NotNull());

			var type = action.GetType();
			for (int i = 0; i < this.InnerList.Count; i++)
			{
				if (this.InnerList[i].GetType() == type && idComparer(this.InnerList[i].ActionId, action.ActionId) == 0)
				{
					var old = this.InnerList[i];
					this.InnerList[i] = action;
					this.CollectionChanged.Raise(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, old, action, i));
					return;
				}
			}
			this.InnerList.Add(action);
			this.CollectionChanged.Raise(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, action, this.Count - 1));
		}

		/// <inheritdoc />
		[MethodImpl(MethodImplOptions.Synchronized)]
		public bool Remove<T>(object id, Comparison<object> idComparer)
			where T : IActionInfo
		{
			Validate.Debug(() => idComparer, v => v.NotNull());

			for (int i = 0; i < this.InnerList.Count; i++)
			{
				if (this.InnerList[i].GetType() == typeof(T) && idComparer(this.InnerList[i].ActionId, id) == 0)
				{
					var old = this.InnerList[i];
					this.InnerList.RemoveAt(i);
					this.CollectionChanged.Raise(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, old, i));
					return true;
				}
			}
			return false;
		}

		/// <inheritdoc />
		[MethodImpl(MethodImplOptions.Synchronized)]
		public void RemoveRange(int start, int count)
		{
			this.InnerList.RemoveRange(start, count);
			this.CollectionChanged.Raise(this, NotifyCollectionChangedAction.Reset);
		}
		#endregion

		#region IList<IActionInfo> Members
		/// <inheritdoc />
		[MethodImpl(MethodImplOptions.Synchronized)]
		public int IndexOf(IActionInfo item)
		{
			Validate.Debug(() => item, v => v.NotNull());

			var type = item.GetType();
			for (int i = 0; i < this.InnerList.Count; i++)
			{
				if (this.InnerList[i].GetType() == type && this.CompareIds(this.InnerList[i].ActionId, item.ActionId) == 0)
					return i;
			}
			return -1;
		}

		/// <inheritdoc />
		[MethodImpl(MethodImplOptions.Synchronized)]
		public void Insert(int index, IActionInfo item)
		{
			throw new NotSupportedException("Insert is not supprted. Use Add instead.");
		}

		/// <inheritdoc />
		[MethodImpl(MethodImplOptions.Synchronized)]
		public void RemoveAt(int index)
		{
			var element = this.InnerList[index];
			this.InnerList.RemoveAt(index);
			this.CollectionChanged.Raise(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, element, index));
		}

		/// <inheritdoc />
		/// <remarks>
		/// This is not thread-safe method and should be used with external locking.
		/// </remarks>
		public IActionInfo this[int index]
		{
			get { return this.InnerList[index]; }
			set
			{
				Validate.Debug(() => value, v => v.NotNull());
				var old = this.InnerList[index];
				this.InnerList[index] = value;
				this.CollectionChanged.Raise(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, old, value, index));
			}
		}
		#endregion

		#region ICollection<IActionInfo> Members
		/// <inheritdoc />
		public int Count
		{
			get { return this.InnerList.Count; }
		}

		/// <inheritdoc />
		public bool IsReadOnly
		{
			get { return false; }
		}

		/// <inheritdoc />
		void ICollection<IActionInfo>.Add(IActionInfo item)
		{
			this.Add(item);
		}

		/// <inheritdoc />
		[MethodImpl(MethodImplOptions.Synchronized)]
		public bool Remove(IActionInfo item)
		{
			Validate.Debug(() => item, v => v.NotNull());
			return this.InnerList.Remove(item);
		}

		/// <inheritdoc />
		[MethodImpl(MethodImplOptions.Synchronized)]
		public void Clear()
		{
			this.InnerList.Clear();
			this.CollectionChanged.Raise(this, NotifyCollectionChangedAction.Reset);
		}

		/// <inheritdoc />
		[MethodImpl(MethodImplOptions.Synchronized)]
		public bool Contains(IActionInfo item)
		{
			return this.InnerList.Contains(item);
		}

		/// <inheritdoc />
		[MethodImpl(MethodImplOptions.Synchronized)]
		public void CopyTo(IActionInfo[] array, int arrayIndex)
		{
			this.InnerList.CopyTo(array, arrayIndex);
		}
		#endregion

		#region IEnumerable<IActionInfo> Members
		/// <summary>
		/// Returns custom enumerator - lock required.
		/// </summary>
		/// <returns></returns>
		public IEnumerator<IActionInfo> GetEnumerator()
		{
			return new SafeEnumerator(this, this.InnerList.GetEnumerator());
		}
		#endregion

		#region IEnumerable Members
		/// <inheritdoc />
		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
		{
			return new SafeEnumerator(this, this.InnerList.GetEnumerator());
		}
		#endregion

		#region Auxiliary methods
		private int CompareIds(object a, object b)
		{
			if (a != null)
				return a.Equals(b) ? 0 : 1;
			if (b != null)
				return b.Equals(a) ? 0 : 1;
			return a == b ? 0 : 1;
		}

		private bool Contains(IActionInfo action, Comparison<object> idComparer)
		{
			Validate.Debug(() => action, v => v.NotNull());
			Validate.Debug(() => idComparer, v => v.NotNull());

			var type = action.GetType();

			for (int i = 0; i < this.InnerList.Count; i++)
			{
				if (this.InnerList[i].GetType() == type && idComparer(this.InnerList[i].ActionId, action.ActionId) == 0)
					return true;
			}
			return false;
		}
		#endregion

		#region Custom enumerator
		/// <summary>
		/// Safe enumerator lock parent objects.
		/// </summary>
		private sealed class SafeEnumerator
			: IEnumerator<IActionInfo>
		{
			#region Private fields
			private readonly object Padlock = null;
			private readonly IEnumerator<IActionInfo> Base = null;
			#endregion

			#region Constructor
			public SafeEnumerator(object padlock, IEnumerator<IActionInfo> en)
			{
				this.Padlock = padlock;
				this.Base = en;

				System.Threading.Monitor.Enter(this.Padlock);
			}
			#endregion

			#region IEnumerator<IActionInfo> Members
			public IActionInfo Current
			{
				get { return this.Base.Current; }
			}
			#endregion

			#region IEnumerator Members
			/// <inheritdoc />
			object System.Collections.IEnumerator.Current
			{
				get { return this.Base.Current; }
			}

			/// <inheritdoc />
			public bool MoveNext()
			{
				return this.Base.MoveNext();
			}

			/// <inheritdoc />
			public void Reset()
			{
				this.Base.Reset();
			}
			#endregion

			#region IDisposable Members
			/// <summary>
			/// Releases lock.
			/// </summary>
			public void Dispose()
			{
				this.Base.Dispose();
				System.Threading.Monitor.Exit(this.Padlock);
			}
			#endregion
		}
		#endregion
	}
}
